/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.wdb.servlet;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;
import org.vacoor.nothing.common.json.Jacksons;
import org.vacoor.nothing.common.misc.util.ZipUtils;
import org.vacoor.nothing.common.util.IOUtils;
import org.vacoor.nothing.common.util.Paths;
import org.vacoor.nothing.common.util.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @author vacoor
 */
//@Controller
//@RequestMapping("/fs")
public class FileSystemServlet extends ResourceServlet {
    private static final String TY_TREE = "tree";
    private static final String TY_BLOB = "blob";
    private ServletContext servletContext;

    public FileSystemServlet() {
        super("support/web");
    }

    /*
    // file?file=xx&contentType=xml&charset=utf-8
    // editor?file=
    @RequestMapping("finder")
    public void finder() {
    }
    */

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        servletContext = config.getServletContext();
    }

    @Override
    protected void handleRequest(String path, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (null == path) {
            resp.sendRedirect(req.getServletPath() + "/");
            return;
        }
        if ("/".equals(path)) {
            req.getRequestDispatcher("/WEB-INF/finder.html").forward(req, resp);
            return;
        }


        path = path.substring(1);

        String reqPath = req.getParameter("path");
        String fs = req.getParameter("fs");
        boolean isFs = "true".equals(fs) || "1".equals(fs);

        if ("ls".equals(path)) {
            resp.setContentType("application/json");
            Map<String, Object> ret = ls(reqPath, isFs, false);
            Jacksons.serialize(resp.getOutputStream(), ret);
        } else if ("cat".equals(path)) {
            cat(reqPath, isFs, resp);
        }
    }

    /**
     * 获取文件清单
     *
     * @param path       请求的资源
     * @param fs         是否是相对文件系统
     * @param showHidden 是否显示隐藏文件
     * @throws java.io.IOException
     */
//    @ResponseBody
//    @RequestMapping("ls")
    public Map<String, Object> ls(final String path, final boolean fs, final boolean showHidden) {
        final String rootFsPath = getFsPath(ROOT_PATH, fs); // 根路径的文件系统路径
        final String reqFsPath = getFsPath(path, fs);       // 请求路径的文件系统路径
        final File f = new File(reqFsPath);

        if (!f.exists()) {
            return ImmutableMap.<String, Object>of("error", "No such file or directory");
        }
        if (!f.isDirectory()) {
            return ImmutableMap.<String, Object>of("error", "file is not a directory");
        }

        // 获取目录下所有目录/文件清单
        // File[] files = getFsFiles(reqFsPath, !showHidden ? VISIBLE_FILE_OR_DIRECOTRY_FILTER : FILE_OR_DIRECTORY_FILTER);
        File[] files = getFsFiles(reqFsPath, FILE_OR_DIRECTORY_FILTER);

        List<Map<String, Object>> data = Lists.transform(Arrays.asList(files), new Function<File, Map<String, Object>>() {
            @Override
            public Map<String, Object> apply(File f) {
                String path = Paths.relativize(f.getAbsolutePath(), rootFsPath);
                String mode = getFileMode(f);
                String type = f.isDirectory() ? TY_TREE : TY_BLOB;
                String url = path;

                // fix windows device path, after relativized C:/ -> C:
                path = Arrays.asList(File.listRoots()).contains(f) ? path + "\\" : path;
                /*
                try {
                    url = URLEncoder.encode(url, "utf-8");
                } catch (UnsupportedEncodingException ignore) {}
                url = servletContext.getContextPath() + "/fs/ls?path=" + url;
                */

                return ImmutableMap.<String, Object>of("path", path, "mode", mode, "type", type, "size", f.length());
            }
        });
        return ImmutableMap.of("path", path, "tree", data);
    }

    /**
     * 查看文件内容
     *
     * @param path
     * @param fs
     * @param response
     * @throws java.io.IOException
     */
//    @RequestMapping("cat")
    public void cat(String path, boolean fs, HttpServletResponse response) throws IOException {
        final String reqFsPath = getFsPath(path, fs);
        final File f = new File(reqFsPath);

        if (!f.exists() || !f.isFile() || !f.canRead()) {
            response.getWriter().write("No such file or file can't read");
            return;
        }

        // 禁止 html 文件被解析
        String n = f.getName();
        if (n.endsWith(".html")) {
            response.setContentType("text/plain");
        }

        // TODO 文件过大就不显示了

        try {
            IOUtils.flow(new FileInputStream(f), response.getOutputStream(), true, true);
        } catch (IOException ioe) {
            response.reset();
            response.getWriter().write("File read failed");
        }
    }

    // 也可参见 dropbox
//    @RequestMapping("cat-hl")
    public void cat2(String path, boolean fs, HttpServletRequest request, HttpServletResponse response) throws URISyntaxException, IOException {
        final String reqFsPath = getFsPath(path, fs);
        File f = new File(reqFsPath);

        if (f.exists() && f.isFile() && f.canRead()) {
            try {
                response.setContentType("text/html");
                response.setCharacterEncoding("utf-8");
                PrintWriter writer = response.getWriter();
                writer.println("<!DOCTYPE html>");
                writer.println("<html>");
                writer.println("<head>");
                // writer.println("<link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/github.min.css\">");
                // writer.println("<link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/googlecode.min.css\">");
                writer.println("<link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/xcode.min.css\">");
                // writer.println("<link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/tomorrow-night-eighties.min.css\">");
                writer.println("<style type=\"text/css\">body,pre { margin:0; padding: 0; font-size:12px;}</style>");
                writer.println("<script src=\"//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.min.js\"></script>");
                writer.println("<script>hljs.initHighlightingOnLoad();</script>");
                writer.println("</head>");
                writer.print("<body><pre><code>");
                String text = IOUtils.readText(f);
                text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&#034;").replace("\'", "&#039;");
//                IOUtils.flow(new FileReader(f), writer, true, false);
                writer.print(text);
                writer.println("</code></pre>");
                writer.println("</body></html>");
            } catch (IOException ignore) {
            }
        }
    }

    /**
     * 创建目录
     */
//    @ResponseBody
//    @RequestMapping("mkdir")
    public Map<String, Object> mkdir(String path, boolean fs) {
        final String reqFsPath = getFsPath(path, fs);
        return ImmutableMap.<String, Object>of("success", new File(reqFsPath).mkdirs(), "path", path);
    }

    // @RequestMapping("mv")
//    @RequestMapping("tail")
    public void tail(String path, int n, boolean fs) throws FileNotFoundException {
        final String reqFsPath = getFsPath(path, fs);
        final File reqFile = new File(reqFsPath);
        final int lines = n;
        /*
        Tailer.create(reqFile, new TailerListenerAdapter() {

        });
        */

        RandomAccessFile raf = new RandomAccessFile(reqFile, "r");
    }

    // create a zero byte empty file
//    @ResponseBody
//    @RequestMapping("touchz")
    public Map<String, Object> touchz(String path, boolean fs) throws IOException {
        final String reqFsPath = getFsPath(path, fs);
        return ImmutableMap.<String, Object>of("success", new File(reqFsPath).createNewFile(), "path", path);
    }

    /**
     * 上传一个文件到给定目录 (copy from local)
     *
     * @param path
     * @param fs
     * @param files
     */
//    @ResponseBody
//    @RequestMapping("put")
    public Map<String, Object> upload(String path, boolean fs, MultipartFile[] files) throws IOException {
        final String reqFsPath = getFsPath(path, fs);
        final File fsDir = new File(reqFsPath);

        for (MultipartFile multipart : files) {
            if (multipart.isEmpty()) {
                continue;
            }
            String filename = multipart.getOriginalFilename();
            String baseName = FilenameUtils.getBaseName(filename);
            String suffix = FilenameUtils.getExtension(filename);

            if (!fsDir.exists()) {
                fsDir.mkdirs();
            } else if (fsDir.isFile()) {
                // TODO throw
            }

            // 查找合适的文件名
            File f = new File(fsDir, filename);
            for (int i = 2; f.exists(); i++) {
                f = new File(fsDir, String.format("%s - %s.%s", baseName, i, suffix));
            }
            multipart.transferTo(f);
        }

        path = Paths.relativize(reqFsPath, getFsPath(ROOT_PATH, fs));
        return ImmutableMap.<String, Object>of("error", "0", "path", path);
    }

    //    @RequestMapping("get")
    public void download(String path, boolean fs, HttpServletResponse response) throws IOException {
        final String reqFsPath = getFsPath(path, fs);
        final File reqFsFile = new File(reqFsPath);

        if (!reqFsFile.exists() || !reqFsFile.isFile() || !reqFsFile.canRead()) {
            //
            return;
        }

        response.setHeader("Content-Disposition", "attachment; filename=" + new String(reqFsFile.getName().getBytes("ISO8859-1"), "UTF-8"));
        IOUtils.flow(new FileInputStream(reqFsFile), response.getOutputStream(), true, true);
    }

    //    @ResponseBody
//    @RequestMapping("zip")
    public void zip(String path, boolean fs, HttpServletResponse response) throws IOException {
        final String reqFsPath = getFsPath(path, fs);
        final File file = new File(reqFsPath);
        if (file.exists()) {
            response.setContentType("application/zip");
            ZipUtils.compressTo(new File[]{file}, response.getOutputStream(), Charset.forName("utf-8"));
        }
    }
//    @RequestMapping("ed")

    /**
     * 获取给定路径在文件系统中的路径
     *
     * @param path 路径信息
     * @param fs   是否是相对于文件系统
     */
    protected String getFsPath(String path, boolean fs) {
        path = null == path || "".equals(path) ? "/" : path;
        path = path.startsWith("/") ? path : "/" + path;
        return Paths.normalize(fs ? path : servletContext.getRealPath(path));
    }

    /**
     * 获取给定文件夹下的所有目录/文件
     *
     * @param fsPath 文件系统路径
     * @param filter 文件过滤器
     */
    protected File[] getFsFiles(String fsPath, FileFilter filter) {
        File f = new File(fsPath);
        // 对根目录特殊处理
        if ("/".equals(fsPath)) {
            File[] roots = File.listRoots();
            List<File> filteredFiles = new ArrayList<File>();
            // fix windows root > 0 当 new File("/a") 为 磁盘根 eg: C:/a
            if (1 < roots.length) {
                for (File root : roots) {
                    if (null == filter || filter.accept(root)) {
                        filteredFiles.add(root);
                    }
                }
                return filteredFiles.toArray(new File[filteredFiles.size()]);
            }
            f = roots[0];
        }
        File[] files = f.listFiles(filter);
        return null != files ? files : new File[0];
    }

    /**
     * 获取文件八进制表示 mode
     */
    protected static String getFileMode(File f) {
        boolean notWindows = -1 < System.getProperty("os.name").toLowerCase().indexOf("windows");
        int mode = notWindows ? getUnixFileMode(f) : 0;

        if (0 == mode) {
            if (f.canRead()) mode += 4;
            if (f.canWrite()) mode += 2;
            if (f.canExecute()) mode += 1;
        }
        return StringUtils.pad(Integer.toOctalString(mode), '0', 6, true);
    }

    /**
     * 获取 Unix 文件 mode
     */
    protected static int getUnixFileMode(File f) {
        int mode = 0;
        if (null != GET_FILE_ATTRIBUTE && f.exists()) {
            try {
                mode = (Integer) GET_FILE_ATTRIBUTE.invoke(null, TO_PATH.invoke(f, new Object[0]), "unix:mode", EMPTY_OPTIONS);
            } catch (InvocationTargetException ignore) {
            } catch (IllegalAccessException ignore) {
            }
        }
        return mode;
    }

    /* *********************************************************
     *
     * *********************************************************/

    private static final String ROOT_PATH = "/";
    private static final Object EMPTY_OPTIONS;          // jdk7 java.nio.file.LinkOption empty instance array
    private static final Method GET_FILE_ATTRIBUTE;     // jdk7 java.nio.file.Files#getFileAttribute method
    private static final Method TO_PATH;                // jdk7 java.io.File#toPath
    private static final FileFilter FILE_OR_DIRECTORY_FILTER = new FileFilter() {
        @Override
        public boolean accept(File f) {
            // 是一个文件或目录(磁盘也属于目录,排除设备等)
            return f.isFile() || f.isDirectory();
        }
    };
    private static final FileFilter VISIBLE_FILE_OR_DIRECOTRY_FILTER = new FileFilter() {
        @Override
        public boolean accept(File f) {
            return !f.isHidden() && (f.isFile() || f.isDirectory());
        }
    };

    static {
        // 尝试加载 JDK 1.7 中获取文件属性相关方法
        Object options;
        Method getAttr;
        Method toPath;
        try {
            Class<?> linkOptionType = Class.forName("java.nio.file.LinkOption");
            Class<?> filesType = Class.forName("java.nio.file.Files");
            Class<?> pathType = Class.forName("java.nio.file.Path");
            options = Array.newInstance(linkOptionType, 0);
            getAttr = filesType.getMethod("getAttribute", pathType, String.class, options.getClass());
            toPath = File.class.getMethod("toPath");
        } catch (ClassNotFoundException ignore) {
            options = getAttr = toPath = null;
        } catch (NoSuchMethodException ignore) {
            options = getAttr = toPath = null;
        }
        EMPTY_OPTIONS = options;
        GET_FILE_ATTRIBUTE = getAttr;
        TO_PATH = toPath;
    }
}
// ftp,
// file, preview
// editor,
// terminal,
// uploader,
// ssh,
// db,
// logging


